﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

namespace WOS.Logging.Impl
{
    public sealed class InMemoryLoggingManager : ILoggingManager
    {
        #region Private Fields
        private List<LogEntry> entries = new List<LogEntry>();
        private DateTime lastCleanUp;
        #endregion

        #region ILoggingManager Members
        public event EventHandler<LogEntryEventArgs> NewEntry;

        private void OnNewEntry(LogEntry entry)
        {
            if (this.NewEntry != null)
                foreach (EventHandler<LogEntryEventArgs> handler in this.NewEntry.GetInvocationList())
                    handler.BeginInvoke(this, new LogEntryEventArgs(entry), null, null);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public LogEntry[] GetAllEntries(params LogSource[] sources)
        {
            this.CleanUp();

            if (sources.Length == 0)
                return this.entries.ToArray();

            var query = from entry in this.entries
                        join source in sources on entry.Source equals source
                        select entry;

            return query.ToArray();
        }
        #endregion
        #region ILogger Members
        public void Log(LogSource source, LogLevel level, string message, params LogAttachment[] attachments)
        {
            try
            {
                this.CleanUp();

                var infos = this.BuildInfos(attachments);
                var entry = new LogEntry(DateTime.Now, source, level, Thread.CurrentThread.ManagedThreadId, message, infos);

                lock (this)
                    this.entries.Add(entry);

                this.OnNewEntry(entry);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                Debug.WriteLine(ex.StackTrace);
            }
        }
        #endregion
        #region Private Members
        private LogAttachmentInfo[] BuildInfos(LogAttachment[] attachments)
        {
            var query = from attachment in attachments
                        select new
                        {
                            Data = attachment.GetData(),
                            Info = new LogAttachmentInfo(attachment.Name, attachment.Type, Path.GetTempFileName())
                        };

            query = query.ToArray();

            foreach (var attachment in query)
                File.WriteAllBytes(attachment.Info.Location, attachment.Data);

            return query.Select(x => x.Info).ToArray();
        }

        private void CleanUp()
        {
            if (this.lastCleanUp + TimeSpan.FromSeconds(15) > DateTime.Now)
                return;

            this.lastCleanUp = DateTime.Now;

            lock (this)
            {
                var stime = DateTime.Now.AddMinutes(-15);
                var ltime = DateTime.Now.AddMinutes(-60);
                var query = from entry in this.entries
                            where entry.Level == LogLevel.Debug && entry.Date < stime
                               || entry.Level == LogLevel.Info && entry.Date < ltime
                            select entry;

                foreach (var entry in query.ToArray())
                {
                    this.entries.Remove(entry);
                    this.RemoveAttachments(entry.GetAttachments());
                }
            }
        }

        private void RemoveAttachments(LogAttachmentInfo[] attachments)
        {
            foreach (var attachment in attachments.ToArray())
            {
                try { File.Delete(attachment.Location); attachment.Location = null; }
                catch (Exception ex) { Debug.Write(String.Format("File {0} cannot be removed: {1}", attachment.Location, ex.Message)); }
            }
        }
        #endregion

        #region IDisposable Members
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Dispose()
        {
            var query = from entry in this.entries
                        from attachment in entry.GetAttachments()
                        select attachment;

            this.RemoveAttachments(query.ToArray());
            this.entries.Clear();
        }
        #endregion
    }
}
